package com.tsing.tsingfengmall.file.controller;

import com.tsing.tsingfengmall.file.exception.BadRequestException;
import com.tsing.tsingfengmall.file.service.IFileService;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import org.hibernate.validator.constraints.Range;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 文件服务前端控制器
 *
 * @author TheTsing
 */
@Validated
@RestController
@RequestMapping("/file")
public class FileController {

    private final IFileService fileService;

    public FileController(IFileService fileService) {
        this.fileService = fileService;
    }

    /**
     * 通过后台服务上传
     * <p>客户端将文件上传到后台服务，后台服务再通过远程调用将文件上传到文件服务
     * <p>这种方法允许你在上传过程中进行额外的处理、验证或转换操作，也可以更好地控制上传过程
     * <p>适用于需要对上传文件进行额外处理或进行权限验证等情况
     *
     * @param file 上传的文件
     * @return 文件url
     */
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<String> upload(@RequestPart("file") MultipartFile file) {
        if (file.isEmpty()) {
            throw new BadRequestException("上传的文件不能为空");
        }
        return ResponseEntity.ok(fileService.upload(file));
    }

    /**
     * 通过后台服务上传
     * <p>客户端将文件上传到后台服务，后台服务再通过远程调用将文件上传到文件服务
     * <p>这种方法允许你在上传过程中进行额外的处理、验证或转换操作，也可以更好地控制上传过程
     * <p>适用于需要对上传文件进行额外处理或进行权限验证等情况
     *
     * @param files 上传的文件集，一次最多上传10个文件
     * @return 文件url集合
     */
    @PostMapping(value = "/uploads", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<List<String>> uploads(@RequestPart("files")
                                                @Size(max = 10, message = "一次最多上传 {max} 个文件")
                                                MultipartFile[] files) {
        if (Arrays.stream(files).anyMatch(MultipartFile::isEmpty)) {
            throw new BadRequestException("上传的文件不能为空");
        }
        return ResponseEntity.ok(fileService.uploads(files));
    }

    /**
     * 后台服务签名后客户端直传
     * <p>客户端请求后台服务生成签名，客户端将签名和目标文件组装成Form表单，再使用Post请求将表单直接上传到对象存储服务（网关地址+对象存储服务路由地址）
     * <p>这种方法可以提高上传性能，将上传的负载转移到客户端，减轻后台服务的负担
     * <p>适用于需要提高上传性能或减少后台服务压力等情况
     *
     * @param name        待上传的文件名称
     * @param maxFileSize 文件最大大小（单位：字节），范围：1B~500MB，默认500M
     * @param expire      有效期（单位：分钟），范围：1分钟~30分钟，默认2分钟
     * @return 签名表单，其中key为文件url，上传成功响应状态码204
     */
    @GetMapping(value = "/policy")
    public ResponseEntity<Map<String, String>> policy(@RequestParam("name")
                                                      @NotBlank(message = "文件名称不能为空")
                                                      String name,

                                                      @RequestParam(value = "maxFileSize", required = false, defaultValue = "524288000")
                                                      @Range(min = 1, max = 524288000, message = "文件最大大小范围为 {min} 字节到 {max} 字节之间")
                                                      Integer maxFileSize,

                                                      @RequestParam(value = "expire", required = false, defaultValue = "2")
                                                      @Range(min = 1, max = 30, message = "有效期范围为 {min} 分钟到 {max} 分钟之间")
                                                      Integer expire) {
        return ResponseEntity.ok(fileService.policy(name, maxFileSize, expire));
    }

    /**
     * 获取文件临时访问地址
     * <p>这种方法可以减轻后台服务的负载，并提高获取文件的效率
     * <p>一般情况下建议使用这种方式获取文件
     *
     * @param url      文件url
     * @param download 访问模式：0 预览；1 下载，默认预览
     * @param expire   有效期（单位：分钟），范围：1分钟~30分钟，默认2分钟
     * @return 访问url，客户端需自行拼上前缀：网关地址+对象存储服务路由地址
     */
    @GetMapping("/access")
    public ResponseEntity<String> access(@RequestParam("url")
                                         @NotBlank(message = "url不能为空")
                                         String url,

                                         @RequestParam(value = "download", required = false, defaultValue = "0")
                                         Boolean download,

                                         @RequestParam(value = "expire", required = false, defaultValue = "2")
                                         @Range(min = 1, max = 30, message = "有效期范围为 {min} 分钟到 {max} 分钟之间")
                                         Integer expire) {
        return ResponseEntity.ok(fileService.access(url, download, expire));
    }

    /**
     * 下载文件
     * <p>这种方法将文件的下载过程完全由后台服务处理，客户端只需要接收文件流即可
     * <p>适用于需要对文件进行额外处理或进行权限验证等情况
     *
     * @param url 文件url
     * @return 文件流
     */
    @GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<Resource> download(@RequestParam("url")
                                             @NotBlank(message = "url不能为空")
                                             String url) {
        return fileService.download(url);
    }

    /**
     * 删除单个文件
     *
     * @param url 文件url
     */
    @DeleteMapping("/delete")
    public ResponseEntity<Void> delete(@RequestParam("url")
                                       @NotBlank(message = "url不能为空")
                                       String url) {
        fileService.delete(url);
        return ResponseEntity.ok().build();
    }

    /**
     * 删除多个文件
     *
     * @param urls 文件url集合，一次最少删除1个文件，最多删除10个文件
     */
    @DeleteMapping("/deletes")
    public ResponseEntity<Void> deletes(@RequestBody
                                        @Size(min = 1, max = 10, message = "一次最少删除 {min} 个文件，最多删除 {max} 个文件")
                                        List<@NotBlank(message = "url不能为空") String> urls) {
        fileService.deletes(urls);
        return ResponseEntity.ok().build();
    }

    /*************** 以下为分片上传 ***************/

    /**
     * 创建分片上传任务，任务过期时间：2小时
     *
     * @param name     文件原始名称
     * @param size     文件大小（单位：字节），范围：100MB~5GB
     * @param partSize 分片大小（单位：字节），范围：5MB~100MB，默认5MB
     * @return 上传信息的映射表，包含以下键值对：
     * - "uploadId":        任务id
     * - "partCount":       分片数量
     * - "uploadUrls":      分片上传url列表，包含partNumber和uploadUrl。使用put请求上传，客户端需自行拼上前缀：网关地址+对象存储服务路由地址
     */
    @GetMapping("/part/init")
    public ResponseEntity<Map<String, Object>> partInit(@RequestParam("name")
                                                        @NotBlank(message = "文件名称不能为空")
                                                        String name,

                                                        @RequestParam(value = "size")
                                                        @Range(min = 104857600, max = 5368709120L, message = "文件大小范围为 {min} 字节到 {max} 字节之间")
                                                        Long size,

                                                        @RequestParam(value = "partSize", required = false, defaultValue = "5242880")
                                                        @Range(min = 5242880, max = 104857600, message = "分片大小范围为 {min} 字节到 {max} 字节之间")
                                                        Integer partSize) {
        return ResponseEntity.ok(fileService.partInit(name, size, partSize));
    }

    /**
     * 合并分片
     *
     * @param uploadId 任务id
     * @return 文件url
     */
    @GetMapping("/part/merge")
    public ResponseEntity<String> partMerge(@RequestParam("uploadId")
                                            @NotBlank(message = "任务id不能为空")
                                            String uploadId) {
        return ResponseEntity.ok(fileService.partMerge(uploadId));
    }

    /**
     * 终止分片上传
     * 删除任务相关的所有已上传分片，减少存储空间浪费
     *
     * @param uploadId 任务id
     */
    @GetMapping("/part/abort")
    public ResponseEntity<Void> partAbort(@RequestParam("uploadId")
                                          @NotBlank(message = "任务id不能为空")
                                          String uploadId) {
        fileService.partAbort(uploadId);
        return ResponseEntity.ok().build();
    }

    /**
     * 获取任务中已经上传的分片列表，用于断点续传
     *
     * @param uploadId 任务id
     * @return 已经上传的分片的partNumber列表
     */
    @GetMapping("/part/uploaded")
    public ResponseEntity<List<Integer>> partUploaded(@RequestParam("uploadId")
                                                      @NotBlank(message = "任务id不能为空")
                                                      String uploadId) {
        return ResponseEntity.ok(fileService.partUploaded(uploadId));
    }

}
